﻿
'compatible with storserver
Imports System.Reflection
Imports std
Imports tcpServer4

Public Class storoomDetector
    Inherits detector

    'copy from storoomc/common.vb
    Private Class command
        Public Const write As String = "wrt"
        Public Const uniqueWrite As String = "uwt"
        Public Const delete As String = "del"
        Public Const modify As String = "mod"
        Public Const read As String = "rad"
        Public Const seek As String = "sek"
        Public Const capacity As String = "cap"
        Public Const valuesize As String = "vsz"
        Public Const list As String = "lst"
        Public Const status As String = "sts"
        Public Const open As String = "opn"
        Public Const close As String = "cls"
        Public Const full As String = "ful"
        Public Const health As String = "hlt"
        Public Const retire As String = "rtr"
        Public Const empty As String = "emp"
        Public Const sizeof As String = "szf"
        Public Const keycount As String = "kct"
        Public Const sync As String = "syn"
        Public Const version As String = "ver"
        Public Const lockwrite As String = "lwr"
        Public Const unlockwrite As String = "ulw"
        Public Const lockread As String = "lrd"
        Public Const unlockread As String = "ulr"
        Public Const lock As String = "lck"
        Public Const unlock As String = "ulc"

        Public Const tsRead As String = "ts.rad"
        Public Const tsWrite As String = "ts.wrt"
        Public Const tsModify As String = "ts.mod"
        Public Const tsSeek As String = "ts.sek"
        Public Const tsList As String = "ts.lst"
        Public Const tsDelete As String = "ts.del"
        Public Const tsSizeof As String = "ts.szf"
        Public Const tsKeycount As String = "ts.kct"
    End Class

    Private Class response
        Public Const success As String = "suc"
        Public Const fail As String = "fal"
        Public Const unknown As String = "unk"
    End Class

    Private Shared ReadOnly commandMapping As map(Of String, job.storage.command)

    Shared Sub New()
        Const tsPrefix As String = "ts"
        Const tsCommandPrefix As String = "ts."
        commandMapping = New map(Of String, job.storage.command)()
        Dim t As Type = Nothing
        t = GetType(job.storage.command)
        For Each s In GetType(command).GetFields(BindingFlags.Public Or BindingFlags.Static)
            Dim cmdstr As String = Nothing
            Dim enumname As String = Nothing
            cmdstr = strtolower(s.GetValue(Nothing))
            If strsame(s.Name, tsPrefix, strlen(tsPrefix)) AndAlso
               strsame(cmdstr, tsCommandPrefix, strlen(tsCommandPrefix)) Then
                enumname = strtolower(strmid(s.Name, strlen(tsPrefix)))
            Else
                enumname = s.Name
            End If
            commandMapping.insert(cmdstr, convertor.cast(Of job.storage.command)([Enum].Parse(t, enumname)))
        Next
    End Sub

    Private WithEvents server As tcpServer4 = Nothing

    Protected NotOverridable Overrides Sub startImpl(ByVal port As Int32, ByVal connectionCount As Int64)
        server = New tcpServer4()
        server.legacyDataSize = True
        server.maxConnectionCount = connectionCount
        If server.start(port) Then
            raiseError("storoomDetector started on port ", port)
        Else
            raiseError("failed to start storoomDetector on port ", port, ", ignore storoomDetector")
            server.stop(0)
        End If
    End Sub

    Public NotOverridable Overrides Sub [stop]()
        server.stop()
    End Sub

    Private Function enoughField(ByVal r As nettransData, ByVal ri As Int64,
                                 ByVal need As Int64, ByVal cmd As String) As Boolean
        If ri + need > r.fieldCount() Then
            raiseError(errorHandle.errorType.user,
                       "receive data field count < receive data index + ", need, " when ", cmd)
            Return False
        Else
            Return True
        End If
    End Function

    Private Function handleCommand(ByVal cmd As String,
                                   ByVal r As nettransData,
                                   ByRef ri As Int64,
                                   ByRef ijob As ijob) As Boolean
        assert(Not isemptystring(cmd))
        assert(Not r Is Nothing)
        assert(ri <= r.size())
        Dim riUnref As Int64 = 0
        riUnref = ri
        Dim enoughField As _do(Of Int64, Boolean) =
            Function(ByRef need As Int64) As Boolean
                If riUnref + need > r.fieldCount() Then
                    raiseError(errorHandle.errorType.user,
                               "receive data field count < receive data index + ", need, " when ", cmd)
                    Return False
                Else
                    Return True
                End If
            End Function
        Dim sjob As storageJob = Nothing
        sjob = New storageJob()
        Select Case cmd
            Case command.delete,
                 command.tsDelete,
                 command.read,
                 command.seek,
                 command.tsSeek,
                 command.sizeof,
                 command.tsSizeof
                If Not enoughField(1) Then
                    Return False
                End If
                sjob.key = r.take(Of String)(inc_(ri))
            Case command.modify, command.uniqueWrite, command.write
                If Not enoughField(2) Then
                    Return False
                End If
                sjob.key = r.take(Of String)(inc_(ri))
                sjob.buff = r.take(inc_(ri))
            Case command.tsModify, command.tsWrite
                If Not enoughField(3) Then
                    Return False
                End If
                sjob.key = r.take(Of String)(inc_(ri))
                sjob.buff = r.take(inc_(ri))
                If Not convertor.toint64(r.take(inc_(ri)), sjob.i64) Then
                    Return False
                End If
            Case command.sync
                If Not enoughField(2) Then
                    Return False
                End If
                sjob.key = r.take(Of String)(inc_(ri))
                If Not convertor.toint64(r.take(Of String)(inc_(ri)), sjob.i64) Then
                    sjob.i64 = npos
                End If
        End Select
        Dim it As map(Of String, job.storage.command).iterator = Nothing
        it = commandMapping.find(cmd)
        If it = commandMapping.end() Then
            sjob.command = job.command.invalid
        Else
            sjob.command = (+it).second
        End If
        ijob = sjob
        Return True
    End Function

    Private Function handleReturn(ByVal cmd As String, ByVal ijob As ijob, ByVal s As nettransData) As Boolean
        assert(Not ijob Is Nothing)
        assert(Not s Is Nothing)
        Dim sjob As storageJob = Nothing
        sjob = convertor.cast(Of storageJob)(ijob)
        If sjob.command = job.command.invalid Then
            Return s.insert(response.unknown)
        ElseIf sjob.command = job.storage.command.version Then
            Return s.insert(sjob.key)
        ElseIf Not sjob.result Then
            Return s.insert(response.fail)
        ElseIf Not s.insert(response.success) Then
            Return False
        Else
            Select Case sjob.command
                Case job.storage.command.capacity,
                     job.storage.command.keycount,
                     job.storage.command.sizeof,
                     job.storage.command.valuesize
                    Return s.insert(Convert.ToString(sjob.i64))
                Case job.storage.command.list
                    If s.insert(Convert.ToString(sjob.vs.size())) Then
                        For i As Int64 = 0 To sjob.vs.size() - 1
                            If Not s.insert(Convert.ToString(sjob.vs(i))) Then
                                Return False
                            End If
                        Next
                        Return True
                    Else
                        Return False
                    End If
                Case job.storage.command.read
                    Dim rtn As Boolean = False
                    rtn = s.insert(sjob.buff)
                    If rtn AndAlso strsame(cmd, command.tsRead) Then
                        rtn = s.insert(convertor.tobytes(sjob.i64))
                    End If
                    Return rtn
                    Return s.insert(+(sjob.buff))
                Case job.storage.command.status
                    Return s.insert(Convert.ToString(sjob.i64)) AndAlso s.insert(Convert.ToString(sjob.i64_2))
                Case Else
                    Return True
            End Select
        End If
    End Function

    Private Class status
        Public cmds As vector(Of String) = Nothing
        Public jobs As vector(Of pointer(Of ijob)) = Nothing
        Public ecs As vector(Of eventComb) = Nothing

        Public Sub New()
            cmds = New vector(Of String)()
            jobs = New vector(Of pointer(Of ijob))()
            ecs = New vector(Of eventComb)()
        End Sub

        Public Sub push(ByVal cmd As String, ByVal job As ijob, ByVal ec As eventComb)
            cmds.push_back(cmd)
            jobs.push_back(New pointer(Of ijob)(job))
            ecs.push_back(ec)
        End Sub
    End Class

    Private Sub server_receive(ByVal handler As talkHandler, ByRef ec As eventComb) Handles server.receive
        Dim ri As Int64 = 0
        Dim r As nettransData = Nothing
        r = handler.receiveData()
        Dim status As status = Nothing
        status = New status()
        While ri < r.size()
            Dim cmd As String = Nothing
            cmd = r.take(Of String)(inc_(ri))
            If isemptystring(cmd) Then
                raiseError(errorHandle.errorType.warning, "cmd is nothing or empty, ignore")
            Else
                strtolower(cmd)
                If isdebugbuild() Then
                    raiseError("receive command ", cmd, " from ", handler)
                End If
                Dim job As ijob = Nothing
                If handleCommand(cmd, r, ri, job) Then
                    status.push(cmd, job, invoke(job))
                Else
                    raiseError(errorHandle.errorType.warning,
                               "cannot handle command ", cmd, " from ", handler, " at ", ri, ", ignore following")
                    Exit While
                End If
            End If
        End While

        If status.ecs.size() = 0 Then
            ec = New eventComb(Function() As Boolean
                                   Dim rec As eventComb = Nothing
                                   Dim s As nettransData = Nothing
                                   s = New nettransData()
                                   Return s.insert(response.fail) AndAlso
                                          handler.return(s, rec) AndAlso
                                          waitfor(rec) AndAlso
                                          goto_end()
                               End Function)
        Else
            ec = New eventComb(Function() As Boolean
                                   Return waitfor(+(status.ecs)) AndAlso
                                          goto_next()
                               End Function,
                               Function() As Boolean
                                   Dim s As nettransData = Nothing
                                   s = New nettransData()
                                   For i As Int64 = 0 To status.jobs.size() - 1
                                       If (status.ecs(i).end_result() AndAlso
                                           Not handleReturn(status.cmds(i), +(status.jobs(i)), s)) OrElse
                                          (Not status.ecs(i).end_result() AndAlso Not s.insert(response.fail)) Then
                                           raiseError(errorHandle.errorType.warning,
                                                      "failed to insert result into response data")
                                       End If
                                   Next
                                   Dim rec As eventComb = Nothing
                                   Return handler.return(s, rec) AndAlso
                                          waitfor(rec) AndAlso
                                          goto_end()
                               End Function)
        End If
    End Sub
End Class
